package com.itjeffrey.autocode.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itjeffrey.autocode.common.ACContext;
import com.itjeffrey.autocode.common.BaseCommon;
import com.itjeffrey.autocode.common.Result;
import com.itjeffrey.autocode.constant.SysConstant;
import com.itjeffrey.autocode.entity.CodeGenerateEntity;
import com.itjeffrey.autocode.entity.LoginInfoEntity;
import com.itjeffrey.autocode.enums.ExceptionEnum;
import com.itjeffrey.autocode.exception.ACException;
import com.itjeffrey.autocode.mapper.CodeGenerateMapper;
import com.itjeffrey.autocode.mapper.LoginInfoMapper;
import com.itjeffrey.autocode.service.CodeGenerateService;
import com.itjeffrey.autocode.service.DbConnTbService;
import com.itjeffrey.autocode.util.CodeGenerator;
import com.itjeffrey.autocode.util.DBUtil;
import com.itjeffrey.autocode.util.TemplateUtil;
import com.itjeffrey.autocode.bo.req.DbLinkBO;
import com.itjeffrey.autocode.bo.resp.ColumnInfo;
import com.itjeffrey.autocode.bo.resp.DataSourceVO;
import com.itjeffrey.autocode.bo.resp.DbLinkVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.*;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @From: Jeffrey
 * @Date: 2020/11/11
 */
@Service
@Slf4j
public class CodeGenerateServiceImpl extends ServiceImpl<CodeGenerateMapper, CodeGenerateEntity> implements
        CodeGenerateService {

    @Resource
    private CodeGenerateMapper codeGenerateMapper;

    @Resource
    private BaseCommon baseCommon;

    @Resource
    private LoginInfoMapper loginInfoMapper;

    @Resource
    private DBUtil dbUtil;

    @Resource
    private DbConnTbService dbConnTbService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DbLinkVo dbLink(DbLinkBO dbLinkBo) {
        DbLinkVo dbLinkVo = new DbLinkVo();
        //校验参数
        if (dbLinkBo == null) {
            throw new ACException(ExceptionEnum.PARAM_EXISTS_NULL);
        }
        //获取数据源
        DataSource dataSource = baseCommon.getDataSource(dbLinkBo);
        Connection connection;
        try {
            //获取连接
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            log.error("[测试数据库连接] 数据库获取连接失败！");
            throw new ACException(ExceptionEnum.DB_LINK_GET_FAILED);
        }
        log.info("数据源：" + dbLinkBo.toString());

        //获取最新连接
        Connection conn = baseCommon.getNewestConn(connection, dbLinkBo.getDatabase());
        //连接信息入库
        dbConnTbService.addDbConnTb(dbLinkBo.getDatabase());

        //根据连接对象获取数据库表名
        try {
            dbLinkVo.setTableNames(baseCommon.getTableNamesByConn(conn, dbLinkBo.getDatabase()));
        } catch (SQLException e) {
            e.printStackTrace();
            throw new ACException(ExceptionEnum.CONN_META_DATA_GET_FAIL);
        }

        //数据库连接主表查询数据源信息
        CodeGenerateEntity codeGenerateEntity = new CodeGenerateEntity();
        setDbSource(dbLinkBo, codeGenerateEntity);
        List<CodeGenerateEntity> dbSources = queryAllDataSource();
        if (CollectionUtils.isEmpty(dbSources)) { //不存在 入库
            codeGenerateEntity.setId(1);
            //数据源数据入库
            int insert = codeGenerateMapper.insert(codeGenerateEntity);
            if (insert != 1) {
                throw new ACException(ExceptionEnum.DATA_SOURCE_INSERT_FAILURE);
            }
        } else { //存在
            for (CodeGenerateEntity dbSource : dbSources) {
                //判断是否有重复连接信息
                if (dbSource.getDbDriver().equals(codeGenerateEntity.getDbDriver()) && dbSource.getDbName()
                        .equals(codeGenerateEntity.getDbName())
                        && dbSource.getIpHost().equals(codeGenerateEntity.getIpHost()) && dbSource.getUserName()
                        .equals(codeGenerateEntity.getUserName())
                        && dbSource.getPswd().equals(codeGenerateEntity.getPswd()) && dbSource.getPortName()
                        .equals(codeGenerateEntity.getPortName())) {
                    //重复 直接返回成功，不做任何处理
                    dbLinkVo.setLinkResult(SysConstant.DB_LINK_SUCCESS);
                    return dbLinkVo;
                }
            }
            //不重复 入库
            int update = codeGenerateMapper.insert(codeGenerateEntity);
            if (update != 1) {
                throw new ACException(ExceptionEnum.DATA_SOURCE_UPDATE_FAILURE);
            }
        }

        //返回结果
        dbLinkVo.setLinkResult(SysConstant.DB_LINK_SUCCESS);
        return dbLinkVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Map<String, String>> codeView(DbLinkBO dbLinkBo) {
        //参数校验
        if (dbLinkBo == null) {
            throw new ACException(ExceptionEnum.PARAM_EXISTS_NULL);
        }

        //根据入参的数据库名称从connMap中获取对应的连接
        Connection conn = baseCommon.getConnection(dbLinkBo.getDatabase());
        if (conn == null) {
            //connMap不存在指定数据库名所对应的连接,则将当前数据源所对应的连接保存到connMap
            baseCommon.checkDbSource(dbLinkBo);
            DataSource dataSource = baseCommon.getDataSource(dbLinkBo);
            try {
                baseCommon.getNewestConn(dataSource.getConnection(), dbLinkBo.getDatabase());
            } catch (SQLException e) {
                log.error("[代码预览]首次预览获取连接失败！");
                e.printStackTrace();
            }
            //insert dbconntb
            dbConnTbService.addDbConnTb(dbLinkBo.getDatabase());
            //根据连接参数判断数据库是否存在
            CodeGenerateEntity codeGenerateEntity = queryByDbsource(dbLinkBo);
            if (codeGenerateEntity == null) {
                //参数入库
                int insert = saveData(dbLinkBo);
                if (insert != 1) {
                    throw new ACException(ExceptionEnum.DATABASE_OPERATE_FAILURE);
                }
            } else {
                int updateData = this.updateData(dbLinkBo);
                if (updateData != 1) {
                    throw new ACException(ExceptionEnum.DATABASE_OPERATE_FAILURE);
                }
            }
        } else {
            //数据修改
            int update = updateData(dbLinkBo);
            if (update != 1) {
                throw new ACException(ExceptionEnum.DATABASE_OPERATE_FAILURE);
            }
        }
        //根据连接和表名获取表字段信息
        List<ColumnInfo> columnInfos = baseCommon.getMysqlColumnInfoByConn(dbLinkBo.getDatabase(), dbLinkBo
                .getTableName(), true);
        List<ColumnInfo> columnInfosOrigin = baseCommon.getMysqlColumnInfoByConn(dbLinkBo.getDatabase(), dbLinkBo
                .getTableName(), false);
        //封装模板数据
        Map<String, Object> modelMap = buildModelData(dbLinkBo, columnInfos, columnInfosOrigin);
        if (CollectionUtils.isEmpty(dbLinkBo.getModelTypes())) {
            //模板类型为空，抛异常
            throw new ACException(ExceptionEnum.MODEL_TYPE_EXISTS_NULL);
        }
        List<Map<String, String>> list = dbLinkBo.getModelTypes().stream().map(modelType -> {
            Map<String, String> map = new HashMap<>();
            //String text = TemplateUtil.parseTemplateAndModel(modelType, modelMap);
            String text = TemplateUtil.outputModelData(modelType, modelMap);
            log.info("模板：" + modelType + " -> " + text);
            map.put(modelType, text);
            return map;
        }).collect(Collectors.toList());
        //返回
        return list;
    }

    @Override
    public Result getcurConnCount() {
        int count = 0;
        //从connMap中查询连接数
        Set<String> keyset = BaseCommon.connMap.keySet();
        for (String key : keyset) {
            Connection connection = baseCommon.getConnection(key);
            if (connection != null) {
                count++;
            }
        }
        return Result.ok(count);
    }

    @Override
    public Result<DataSourceVO> upload(MultipartFile file) {
        log.info("数据源文件开始上传...");
        //校验
        if (file.isEmpty()) {
            throw new ACException(ExceptionEnum.UPLOAD_FILE_EXISTS_NULL);
        }
        String originalFilename = file.getOriginalFilename();
        String ext = originalFilename.substring(originalFilename.indexOf(SysConstant.SPECIAL_CHAR_13) + 1);
        if (!SysConstant.FILE_EXT_TYPE.equals(ext)) {
            throw new ACException(ExceptionEnum.UPLOAD_FILE_EXT_MISMATCH);
        }
        if (file.getSize() > SysConstant.MAX_SQL_SCRIPT_SIZE) {
            throw new ACException(ExceptionEnum.UPLOAD_DATASOURCE_FILE_EXCEED_SIZE, file.getSize() / 1024 +
                    SysConstant.FILE_SIZE_UNIT_KB, SysConstant.MAX_SQL_SCRIPT_SIZE / 1024 + SysConstant
                    .FILE_SIZE_UNIT_KB);
        }

        String pathname = System.getProperty(SysConstant.USER_DIR) + SysConstant.UPLOAD_ABSTRACT_PATH;
        File destFile = new File(pathname);
        if (!destFile.exists()) {
            destFile.mkdirs();
        }
        String fileName = UUID.randomUUID().toString() + SysConstant.SPECIAL_CHAR_13 + SysConstant.FILE_EXT_TYPE;
        destFile = new File(destFile, fileName);
        if (destFile.exists()) {
            throw new ACException(ExceptionEnum.UPLOAD_FILE_ALREADY_EXISTS);
        }

        //上传
        try {
            file.transferTo(destFile);
            log.info("数据源文件已上传到服务器...");
        } catch (IOException e) {
            throw new ACException(ExceptionEnum.UPLOAD_FILE_FAIL);
        }

        //解析上传的文件，获取连接
        DbLinkBO dbLinkBo = null;
        try {
            dbLinkBo = analizeFile(pathname, fileName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ACException(ExceptionEnum.FILE_EXPRESS_FAIL);
        }
        //获取连接
        DbLinkVo dbLinkVo;
        try {
            dbLinkVo = this.dbLink(dbLinkBo);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ACException(ExceptionEnum.GET_CONN_FAIL);
        }
        DataSourceVO dataSourceVO = new DataSourceVO();
        BeanUtils.copyProperties(dbLinkBo, dataSourceVO);
        dataSourceVO.setTableNames(dbLinkVo.getTableNames());
        //文件已上传，获取连接成功！
        log.info("数据源文件上传并解析成功！");
        return new Result("文件已上传并解析成功！", dataSourceVO);
    }

    @Override
    public void oneKeyGenerate(DbLinkBO dbLinkBo) {
        log.info("开始一键生成代码...");
        String url = "jdbc:mysql://" + dbLinkBo.getIp() + SysConstant.SPECIAL_CHAR_15 + dbLinkBo.getPort() +
                SysConstant.SPECIAL_CHAR_16 + dbLinkBo.getDatabase() +
                "?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT";
        String driver = dbLinkBo.getDbDriver();
        String username = dbLinkBo.getUsername();
        String password = dbLinkBo.getPassword();
        String pkgName = dbLinkBo.getPkgName();
        String proName = dbLinkBo.getProName();
        //从上下文中获取登录名
        String loginName = ACContext.getUserContext().getUsername();
        LoginInfoEntity entity = loginInfoMapper.selectById(loginName);
        if (entity == null) {
            throw new ACException(ExceptionEnum.LOGIN_INFO_NOT_EXISTS);
        }
        CodeGenerator codeGenerator = new CodeGenerator(url, driver, username, password, entity.getUserName(),
                pkgName, proName);
        try {
            codeGenerator.getAutoGenerator(dbLinkBo.getTableName()).execute();
            log.info("代码生成完毕！");
        } catch (Exception e) {
            log.error("代码生成异常！", e);
        }
    }

    @Override
    public void dsFileModuleDownload(HttpServletRequest request, HttpServletResponse response) {
        log.info("开始文件模板下载...");
        String filepath = System.getProperty(SysConstant.USER_DIR) + SysConstant.STATIC_ABSTRACT_PATH;
        String filename = SysConstant.MODEL_DOWN_FILE_NAME;
        File file = new File(filepath, filename);
        if (!file.exists()) {
            throw new ACException(ExceptionEnum.FILE_MODULE_NOT_EXISTS);
        }
        BufferedInputStream bis = null;
        OutputStream os = null;
        try {
            response.reset();
            response.setContentType("application/x-download");
            response.setCharacterEncoding(SysConstant.CHARSET_UTF8);
            response.setContentLength((int) file.length());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename,
                    SysConstant.CHARSET_UTF8));
            response.setHeader("Access-Control-Allow-Origin", SysConstant.SPECIAL_CHAR_9); //设置跨域访问
            int len;
            byte[] bytes = new byte[1024];
            bis = new BufferedInputStream(new FileInputStream(file));
            os = response.getOutputStream();
            while ((len = bis.read(bytes)) != -1) {
                os.write(bytes, 0, len);
                os.flush();
            }
        } catch (IOException e) {
            log.error("文件模板下载失败！");
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        log.info("文件模板下载成功！");
    }

    private int saveData(DbLinkBO dbLinkBo) {
        CodeGenerateEntity entity = new CodeGenerateEntity();
        this.setDbSource(dbLinkBo, entity);
        entity.setTableName(dbLinkBo.getTableName());
        entity.setProjectName(dbLinkBo.getProName());
        entity.setPkgName(dbLinkBo.getPkgName());
        entity.setModelTypes(JSON.toJSONString(dbLinkBo.getModelTypes()));
        return codeGenerateMapper.insert(entity);
    }

    private int updateData(DbLinkBO dbLinkBo) {
        UpdateWrapper<CodeGenerateEntity> wrapper = new UpdateWrapper<>();
        wrapper.eq("db_driver", dbLinkBo.getDbDriver());
        wrapper.eq("ip_host", dbLinkBo.getIp());
        wrapper.eq("port_name", dbLinkBo.getPort());
        wrapper.eq("db_name", dbLinkBo.getDatabase());
        wrapper.eq("user_name", dbLinkBo.getUsername());
        wrapper.eq("pswd", dbLinkBo.getPassword());
        CodeGenerateEntity entity = new CodeGenerateEntity();
        entity.setTableName(dbLinkBo.getTableName());
        entity.setProjectName(dbLinkBo.getProName());
        entity.setPkgName(dbLinkBo.getPkgName());
        entity.setModelTypes(JSON.toJSONString(dbLinkBo.getModelTypes()));
        return codeGenerateMapper.update(entity, wrapper);
    }

    /**
     * 解析上传的数据源文件
     *
     * @param pathname
     * @param fileName
     */
    private DbLinkBO analizeFile(String pathname, String fileName) throws Exception {
        DbLinkBO dbLinkBo;
        try {
            List<String> list = new ArrayList<>();
            BufferedReader bufferedReader;
            try {
                bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(pathname + SysConstant
                        .SPECIAL_CHAR_17 + fileName), SysConstant.CHARSET_UTF8));
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    if (StringUtils.isNotBlank(line) && !line.contains(SysConstant.SPECIAL_CHAR_5)) {
                        list.add(line);
                    }
                }
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            Map<String, String> map = list.stream().collect(Collectors.toMap(s -> s.substring(0, s.indexOf
                    (SysConstant.SPECIAL_CHAR_14)), s -> s.substring(s.indexOf(SysConstant.SPECIAL_CHAR_14) + 1)));
            dbLinkBo = new DbLinkBO();
            dbLinkBo.setDbDriver(map.get("driver"));
            dbLinkBo.setIp(map.get("ip"));
            dbLinkBo.setPort(map.get("port"));
            dbLinkBo.setDatabase(map.get("database"));
            dbLinkBo.setUsername(map.get("username"));
            dbLinkBo.setPassword(map.get("password"));
        } catch (Exception e) {
            throw new Exception(e);
        }
        return dbLinkBo;
    }

    /**
     * 构建模板数据
     *
     * @param dbLinkBo
     * @return
     */
    private Map<String, Object> buildModelData(DbLinkBO dbLinkBo, List<ColumnInfo> columnInfos, List<ColumnInfo> columnInfosOrigin) {
        HashMap<String, Object> modelMap = new HashMap<>();
        modelMap.put("database", dbLinkBo.getDatabase());
        modelMap.put("proName", dbLinkBo.getProName());
        modelMap.put("pkgName", dbLinkBo.getPkgName());
        modelMap.put("tableName_origin", dbLinkBo.getTableName());
        modelMap.put("tableName", dbUtil.camelNameForColumn(dbLinkBo.getTableName()));
        modelMap.put("columnInfos", columnInfos);
        modelMap.put("columnInfos_origin", columnInfosOrigin);
        modelMap.put("mappingUrl", dbUtil.getMappingUrl(dbLinkBo.getTableName()));
        modelMap.put("author", ACContext.getUserContext().getUsername());
        modelMap.put("date", new SimpleDateFormat(SysConstant.SDF_DATE_TIME_FORMARTER).format(new Date()));
        return modelMap;
    }

    /**
     * 设置数据源入库参数
     *
     * @param dbLinkBo
     * @param codeGenerateEntity
     */
    private void setDbSource(DbLinkBO dbLinkBo, CodeGenerateEntity codeGenerateEntity) {
        codeGenerateEntity.setDbDriver(dbLinkBo.getDbDriver());
        codeGenerateEntity.setIpHost(dbLinkBo.getIp());
        codeGenerateEntity.setPortName(dbLinkBo.getPort());
        codeGenerateEntity.setDbName(dbLinkBo.getDatabase());
        codeGenerateEntity.setUserName(dbLinkBo.getUsername());
        codeGenerateEntity.setPswd(dbLinkBo.getPassword());
    }

    /**
     * 查询所有数据源信息
     *
     * @return
     */
    private List<CodeGenerateEntity> queryAllDataSource() {
        QueryWrapper<CodeGenerateEntity> queryWrapper = new QueryWrapper<>();
        return codeGenerateMapper.selectList(queryWrapper);
    }

    private CodeGenerateEntity queryByDbsource(DbLinkBO dbLinkBo) {
        QueryWrapper<CodeGenerateEntity> queryWrapper = new QueryWrapper<>();
        CodeGenerateEntity codeGenerateEntity = new CodeGenerateEntity();
        this.setDbSource(dbLinkBo, codeGenerateEntity);
        queryWrapper.setEntity(codeGenerateEntity);
        return codeGenerateMapper.selectOne(queryWrapper);
    }

}
